1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.graphics.g2d.textrenderer;
12 import hip.graphics.mesh;
13 import hip.math.matrix;
14 import hip.api.data.font;
15 import hip.hiprenderer;
16 import hip.assetmanager;
17 public import hip.graphics.orthocamera;
18 public import hip.api.graphics.batch;
19 public import hip.api.graphics.text : HipTextAlign, Size;
20 
21 /**
22 *   Don't change those names. If the variable names are changed, the shaders should stop working
23 */
24 @HipShaderInputLayout struct HipTextRendererVertex
25 {
26     import hip.math.vector;
27     Vector3 vPosition;
28     Vector2 vTexST;
29 
30     this(Vector3 vPosition, Vector2 vTexST)
31     {
32         this.vPosition = vPosition;
33         this.vTexST = vTexST;
34     }
35 
36     static enum size_t floatsCount = (HipTextRendererVertex.sizeof / float.sizeof);
37     static enum size_t quadsCount = floatsCount*4;
38 }
39 
40 @HipShaderVertexUniform("Cbuf")
41 struct HipTextRendererVertexUniforms
42 {
43     Matrix4 uMVP = Matrix4.identity;
44 }
45 
46 @HipShaderFragmentUniform("FragVars")
47 struct HipTextRendererFragmentUniforms
48 {
49     float[4] uColor = [1,1,1,1];
50 }
51 
52 
53 private __gshared Shader bmTextShader = null;
54 
55 /**
56 *   This class oculd be refactored in the future to actually
57 * use a spritebatch for its drawing.
58 */
59 class HipTextRenderer : IHipBatch
60 {
61     IHipFont font;
62     Mesh mesh;
63     index_t[] indices;
64     HipTextRendererVertex[] vertices;
65 
66     protected HipColor color;
67     protected HipOrthoCamera camera;
68     protected float managedDepth = 0;
69     private uint quadsCount;
70     private uint lastDrawQuadsCount;
71     bool shouldRenderLineBreak, shouldRenderSpace;
72     private __gshared uint[] linesWidths;
73 
74     this(HipOrthoCamera camera, index_t maxQuads = DefaultMaxSpritesPerBatch)
75     {
76         import hip.error.handler;
77         import hip.util.conv:to;
78         if(bmTextShader is null)
79         {
80             import hip.hiprenderer.initializer;
81             bmTextShader = newShader(HipShaderPresets.BITMAP_TEXT);
82             bmTextShader.addVarLayout(ShaderVariablesLayout.from!(HipTextRendererVertexUniforms)(HipRenderer.getInfo));
83             bmTextShader.addVarLayout(ShaderVariablesLayout.from!(HipTextRendererFragmentUniforms)(HipRenderer.getInfo));
84             bmTextShader.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD);
85             const Viewport v = HipRenderer.getCurrentViewport();
86             bmTextShader.uMVP = Matrix4.orthoLH(0, v.width, v.height, 0, 0.01, 100);
87             bmTextShader.setDefaultBlock("FragVars");
88             bmTextShader.sendVars();
89         }
90         ErrorHandler.assertLazyExit(index_t.max > maxQuads * 6, "Invalid max quads. Max is "~to!string(index_t.max/6));
91         mesh = new Mesh(HipVertexArrayObject.getVAO!HipTextRendererVertex, bmTextShader);
92         //6 indices per quad
93         vertices = new HipTextRendererVertex[](maxQuads*4);
94         mesh.setIndices(HipRenderer.getQuadIndexBuffer(maxQuads));
95         mesh.createVertexBuffer(cast(index_t)vertices.length, HipResourceUsage.Dynamic);
96         mesh.sendAttributes();
97         mesh.setVertices(vertices);
98         if(camera is null)
99             camera = new HipOrthoCamera();
100         this.camera = camera;
101 
102         import hip.global.gamedef;
103         //Promise it won't modify
104         setFont(cast(IHipFont)HipDefaultAssets.font);
105     }
106 
107     void setCurrentDepth(float depth){managedDepth = depth;}
108 
109     void setFont(IHipFont font)
110     {
111         if(this.font !is null && font !is this.font)
112         {
113             draw();
114         }
115         this.font = font;
116     }
117 
118     void setColor(HipColor color)
119     {
120         if(this.color != color)
121         {
122             if(this.color != HipColor.no)
123                 draw();
124             bmTextShader.uColor = HipColorf(color);
125         }
126         this.color = color;
127     }
128 
129     /**
130      * Implementation for unchanging text.
131      *  The text will be saved, represented as an internal ID to a managed static HipText. Which means the texture will be baked
132      *  so it is possible to actually draw it a lot faster as all the preprocessings are done once.
133      */
134     void draw(string str, int x, int y, float scale = 1, HipTextAlign align_ = HipTextAlign.centerLeft, Size bounds = Size.init, bool wordWrap = false)
135     {
136         import hip.api.graphics.text;
137         int vI = quadsCount*4; //vertex buffer index
138         if(vI + str.length * 4 > vertices.length)
139         {
140             flush;
141             vI = 0;
142         }
143 
144         vI+= putTextVertices(font, (cast(HipTextRendererVertexAPI[])vertices)[vI..$], str, x, y, managedDepth, scale, align_, bounds, wordWrap, shouldRenderSpace);
145         quadsCount = vI/4;
146     }
147 
148     ///This way it will reallocate once.
149     void addVertices(void[] vertices, IHipFont font)
150     {
151         if(vertices.length > 0)
152         {
153             setFont(font);
154             HipTextRendererVertex[] theVerts = cast(HipTextRendererVertex[])vertices;
155             if(theVerts.length+quadsCount*4 > this.vertices.length)
156                 this.vertices.length = theVerts.length+quadsCount*4;
157             this.vertices[quadsCount*4..quadsCount*4+theVerts.length] = theVerts[0..$];
158             quadsCount+= theVerts.length/4;
159         }
160     }
161 
162     void draw()
163     {
164         if(font is null)
165         {
166             import hip.error.handler;
167             ErrorHandler.showWarningMessage("Font Missing", "No font attached on HipTextRenderer");
168             return;
169         }
170 
171         if(quadsCount - lastDrawQuadsCount != 0)
172         {
173             mesh.bind();
174             this.font.texture.bind();
175             mesh.shader.setVertexVar("Cbuf.uMVP", camera.getMVP(), true);
176             mesh.shader.sendVars();
177 
178             size_t start = lastDrawQuadsCount*4;
179             size_t end = quadsCount*4;
180 
181             mesh.updateVertices(vertices[start..end], cast(int)start);
182 
183             mesh.draw((quadsCount - lastDrawQuadsCount)*6, HipRendererMode.triangles, lastDrawQuadsCount*6);
184             font.texture.unbind();
185             mesh.unbind();
186         }
187         lastDrawQuadsCount = quadsCount;
188     }
189     /**
190     *   Flush should be took care since it could make it rewrite to the same part of the buffer agin.
191     *   While shadow buffering is not implemented, use it as that.
192     */
193     void flush()
194     {
195         draw();
196         lastDrawQuadsCount = quadsCount = 0;
197     }
198 }